﻿using ASmile.Com;
using ASmile.Com.Da;
using ASmile.DBModel;
using ASmile.ORM.Cores;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ASmile.Web
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
    public class LogFilterAttribute : Attribute, IActionFilter, IResourceFilter
    {
        static LogFilterAttribute()
        {
            if (IsSaveLog)
            {
                RunToDBTask();
            }
        }

        static bool IsSaveLog { get; } = Config.THIS.LogHttpSave;

        public LogFilterAttribute() : this(true)
        { }

        public LogFilterAttribute(bool writeLog)
        {
            WiriteLog = writeLog && Config.THIS.LogHttpSave;
        }
        /// <summary>
        /// 是否保存日志
        /// </summary>
        bool WiriteLog { get; }

        bool IsWriteLog(FilterContext context)
        {
            if (!WiriteLog) return false;
            foreach (var item in context.Filters)
            {
                if (item is LogFilterAttribute logFilter)
                {
                    if (logFilter.WiriteLog == false) return false;
                }
            }
            return true;
        }

        static Dictionary<string, Log_Http> LogDicts { get; } = new Dictionary<string, Log_Http>();

        string GetDictKey(HttpContext context)
        {
            return $"{context.Connection.Id}:{context.Request.Path}";
        }

        bool GetLogModel(HttpContext context, out Log_Http log, bool isRemove = false)
        {
            string key = GetDictKey(context);
            var r = LogDicts.TryGetValue(key, out log);
            if (r && isRemove)
            {
                LogDicts.Remove(key);
            }
            return r;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (!IsWriteLog(context)) return;

            if (context.Result is ObjectResult rObj && rObj.Value != null)
            {
                if (GetLogModel(context.HttpContext, out Log_Http log))
                {
                    log.RetValue = rObj.Value;
                }
            }
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            if (!IsWriteLog(context)) return;
            if (GetLogModel(context.HttpContext, out Log_Http log))
            {
                var ctrll = context.Controller as BaseApiController;
                log.AddUser = ctrll?.CurrUser?.UserName;

                if (context.ActionArguments?.Count > 0)
                {
                    log.ReqValue = context.ActionArguments.First().Value;
                }
            }
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            if (!IsWriteLog(context)) return;

            if (GetLogModel(context.HttpContext, out Log_Http log, true))
            {
                log.ReqLength = (int)(context.HttpContext.Request.ContentLength ?? -1);
                log.RepLength = (int)(context.HttpContext.Response.ContentLength ?? -1);

                log.Path = context.HttpContext.Request.Path;
                log.AddHost = context.HttpContext.GetRemoteIp();
                log.ReqMethod = context.HttpContext.Request.Method;
                log.Interval = (int)(DateTime.Now - log.LogTime).TotalMilliseconds;
                log.RepStatus = context.HttpContext.Response.StatusCode;

                AddLog(log);
            }
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            if (!IsWriteLog(context)) return;

            var key = GetDictKey(context.HttpContext);

            LogDicts[key] = new Log_Http
            {
                LogTime = DateTime.Now
            };
        }

        static Queue<Log_Http> LogQueues { get; } = new Queue<Log_Http>();

        static AutoResetEvent LogTaskEvent { get; } = new AutoResetEvent(false);

        void AddLog(Log_Http model)
        {
            if (!IsSaveLog) return;

            LogQueues.Enqueue(model);
            LogTaskEvent.Set();
        }

        static void RunToDBTask()
        {
            if (!IsSaveLog) return;

            Task.Run(ToDBTask);
            void ToDBTask()
            {
                var saveDetail = Config.THIS.LogHttpSaveDetail;
                var log = LogHelper.CreateLog("LogHttp");
                var db = new DBContextDebug(DBMain.DBType, ConfigCom.M.LogDbConnStr);
                log.Info("开始Http日志保存任务");
                while (true)
                {
                    try
                    {
                        if (LogQueues.Count > 0)
                        {
                            var m = LogQueues.Dequeue();
                            if (m == null) continue;

                            if (m.RetValue != null)
                            {
                                if (m.RetValue is ApiResult ret)
                                {
                                    m.RetCode = ret.code;
                                    m.RetMsg = ret.msg;
                                }
                            }

                            if (saveDetail)
                            {
                                if (m.ReqValue != null)
                                {
                                    m.ReqData = JsonHelper.Serialize(m.ReqValue);
                                }
                                //if (m.RetValue != null)
                                //{
                                //    m.RetData = JsonHelper.Serialize(m.RetValue);
                                //}
                            }

                            db.Insert(m);
                            continue;
                        }
                    }
                    catch (Exception ex)
                    {
                        log.Error("保存 http log 出错", ex);
                    }
                    LogTaskEvent.WaitOne(TimeSpan.FromMinutes(1));
                }
            }
        }
    }
}